/*
 * DIM-SUM操作系统
 *
 * Copyright (C) 2023 国科础石(重庆)软件有限公司
 *
 * 作者: Dong Peng <w-pengdong@kernelsoft.com>
 *
 * License terms: GNU General Public License (GPL) version 3
 *
 */
#include <dim-sum/bug.h>
#include <dim-sum/kallsyms.h>
#include <dim-sum/kernel.h>
#include <dim-sum/sched.h>
#include <dim-sum/stacktrace.h>

#include <asm/exception.h>

int notrace user_unwind_frame(struct stackframe *frame);

static void dump_backtrace_entry(unsigned long where, unsigned long stack)
{
	print_ip_sym(where);
}

static void user_dump_backtrace_entry(unsigned long where, unsigned long stack)
{
	print_ip_sym(where);
}

int notrace unwind_frame(struct stackframe *frame)
{
	unsigned long high, low;
	unsigned long fp = frame->fp;
	unsigned long sfp = fp - 16;
	low  = frame->sp;
	high = ALIGN(low, PROCESS_STACK_SIZE);
	
	if (fp < low || fp > high - 0x18 || fp & 0xf)
		return -EINVAL;

	frame->sp = fp;
	frame->fp = *((unsigned long *)(sfp));
	frame->pc = (*(unsigned long *)(sfp + 8)) - 4;

	return 0;
}

int notrace user_unwind_frame(struct stackframe *frame)
{
	unsigned long high, low;
	unsigned long fp = frame->fp;

	low  = frame->sp;
	high = ALIGN(low, USER_STACK_SIZE);

	if (fp < low || fp > high - 0x18 || fp & 0xf)
		return -EINVAL;

	frame->sp = fp + 0x10;
	frame->fp = *(unsigned long *)(fp);
	frame->pc = *(unsigned long *)(fp + 8);

	return 0;
}

static void dump_backtrace(struct exception_spot *regs, struct task_desc *tsk)
{
	struct stackframe frame;

	register unsigned long sp asm ("sp");

	pr_debug("%s(regs = %p tsk = %p)\n", __func__, regs, tsk);

	if (!tsk)
		tsk = current;

	if (regs) {
		frame.fp = regs->s0; //fp
		frame.sp = regs->sp;
		frame.pc = regs->sepc;
	} else if (tsk == current) {
		frame.fp = (unsigned long)__builtin_frame_address(0);
		frame.sp = sp;
		frame.pc = (unsigned long)dump_backtrace;
	} else {
		frame.fp = thread_saved_fp(tsk);
		frame.sp = thread_saved_sp(tsk);
		frame.pc = thread_saved_pc(tsk);
	}

	pr_emerg("Call trace:\n");
	while (1) {
		unsigned long where = frame.pc;
		int ret;

		ret = unwind_frame(&frame);
		if (ret < 0)
			break;
		dump_backtrace_entry(where, frame.sp);
	}
}

void dump_task_stack(struct task_desc *tsk, unsigned long *sp)
{
	dump_backtrace(NULL, tsk);
	barrier();
}


static void dump_user_backtrace(struct exception_spot *regs, struct task_desc *tsk)
{
	struct stackframe frame;
	register unsigned long sp asm ("sp");

	if (!tsk)
		tsk = current;

	if (regs) {
		frame.fp = regs->s0; //fp
		frame.sp = regs->sp;
		frame.pc = regs->sepc;
	} else if (tsk == current) {
		frame.fp = (unsigned long)__builtin_frame_address(0);
		frame.sp = sp;
		frame.pc = (unsigned long)dump_backtrace;
	}
	pr_emerg("Call trace user addr:\n");
	while (1) {
		unsigned long where = frame.pc;
		int ret;

		ret = user_unwind_frame(&frame);
		if (ret < 0)
			break;
		user_dump_backtrace_entry(where, frame.sp);
	}
}

void dump_task_user_stack(struct exception_spot *regs, struct task_desc *tsk)
{
	dump_user_backtrace(regs, tsk);
	barrier();
}
